package com.espirit.eap.manager;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.espirit.eap.ValidationUtil;
import com.espirit.eap.annotation.Dict;
import com.espirit.eap.annotation.ModelDisplayProperty;
import com.espirit.eap.manager.workflow.WFInstance;
import com.espirit.eap.pagelayout.AvabButton;
import com.espirit.eap.pagelayout.DetailLayout;
import com.espirit.eap.pagelayout.Element;
import com.espirit.eap.pagelayout.ElementService;
import com.espirit.eap.pagelayout.FlatSection;
import com.espirit.eap.pagelayout.FunctionButton;
import com.espirit.eap.pagelayout.GridSection;
import com.espirit.eap.pagelayout.Layout;
import com.espirit.eap.pagelayout.LayoutService;
import com.espirit.eap.pagelayout.LinkedButton;
import com.espirit.eap.pagelayout.ListLayout;
import com.espirit.eap.pagelayout.Model;
import com.espirit.eap.pagelayout.ModelField;
import com.espirit.eap.pagelayout.ModelService;
import com.espirit.eap.pagelayout.Page;
import com.espirit.eap.pagelayout.PageReference;
import com.espirit.eap.pagelayout.SectionElement;
import com.espirit.eap.pagelayout.TabbedSection;
import com.espirit.eap.util.Functions;
import com.espirit.eap.util.tagAdaptor.TagAdaptorUtil;
import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.Invoker;
import com.googlecode.cswish.struts.Routing;
import com.googlecode.cswish.struts.hibernate.HibernateUtil;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.ModelPropertyInfo;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.ClassUtil;
import com.googlecode.cswish.util.FrameConstant;
import com.googlecode.cswish.util.OgnlExUtil;
import com.opensymphony.xwork2.util.ClassLoaderUtil;

public abstract class ModelBuilderSupport implements ModelBuilder {
	protected final static String SEARCH = "search";
	protected final static String SEARCHRELATEDRESULT = "searchRelatedResult";
	private final static Boolean NEED_ECCN = false;
	
	private final static Boolean FORCE_DELETE_ALL_LAYOUT = true;
	
	protected final static String REQUIRED = "required";
	
	@Resource
	protected BeanIntrospector beanIntrospector;

	@Resource
	protected GenericService genericService;

	@Resource
	ObjectFactory objectFactory;

	@Resource
	protected OgnlExUtil ognlExUtil;

	@Resource
	protected ModelService modelService;
	
	@Resource
	protected LayoutService layoutService;

	@Resource
	protected I18nService i18nService;
	
	@Resource 
	protected ValidationUtil genericServiceUtil;
	
	@Resource 
	protected ElementService elementService;
	
	@Resource
	SystemDataService systemDataService;
	
	@Resource
	protected Functions functions;
	
	@PersistenceContext(unitName="espirit")
	protected EntityManager em;
	
	@Resource
	private Routing routing;

	protected Class<?> clazz;

	private List<ModelField> modelFields = new ArrayList<ModelField>();
	protected List<Element> searchFields = new ArrayList<Element>();
	protected List<Element> listElements = new ArrayList<Element>();
	protected List<Element> detailElements = new ArrayList<Element>();

	protected Model model = null;
	private String modelCnName = null;
	private String modelEnName = null;
	private List<AvabButton> defaultButtons;
	private ModelInfo mi = null;
	private boolean existed = false;
	public static List<Model> models = new ArrayList<Model>();
//	public static HashMap<Element, I18n> elementI18ns = new HashMap<Element, I18n>();
	private static final Log logger = LogFactory
			.getLog(ModelBuilderSupport.class);

	private final int COLS_PERROW = 1; //2
	
	private final int LIST_COLS_PERROW = 100;

	private Map<String, List<Element>> extraListElements;
	
	private Map<Integer, String> pathLayoutMapping;
	
	final String[] deptTrees = new String[]{"deptIds", "department"};
	final String[] workGroupIds = new String[] {"workGroupIds"};
	final String[] buildingTrees = new String[]{"buildingIds", "building"};

	@Override
	public abstract boolean init();
	
	@Override
	public void clean(){
//		em.flush();
//		em.clear();
		
		modelFields = new ArrayList<ModelField>();
		searchFields = new ArrayList<Element>();
		listElements = new ArrayList<Element>();
		detailElements = new ArrayList<Element>();
		models = new ArrayList<Model>();
		
		defaultButtons = null;
		model = null;
		
		buildFieldsInfoOk = false;
		this.buildFieldsOk = false;
		this.buildLayoutOk = false;
		this.buildMiscDataOk = false;
		pathLayoutMapping =null;
		exProperties = new HashMap<String, String[]>();
		extraListElements = null;
		modelCnName = null;
		modelEnName = null;
		
		this.buttons = null;
		existed = false;
	}
	protected boolean setClass(Class<?> clazz, String enName, String cnName) {
		logger.debug("init model:" + clazz.getName());
		gatherExproperties();
		
		if (this.existsClass(clazz)){
			if (model == null){
				model = getModel(clazz);
			}
			if (model == null){
				logger.debug("system null:" +clazz.getName());
			}
			
			List<Layout> layouts = new ArrayList<Layout>(model.getLayouts());
			pathLayoutMapping = new HashMap<Integer, String>();
			for (Layout layout : layouts) {
				String ql = " from " + Page.class.getName() + " a where a.layout.id=?";
				List<Page> refPages = genericService.searchByQl(new QLInfo(ql, true, layout.getId()), false).getData();
				for (Page refPage : refPages) {
					Layout pageLayout = refPage.getLayout();
					if (forceUseSystemLayout()){
						refPage.setLayout(null);
						genericService.merge(refPage);
					} else {
						if (pageLayout != null && pageLayout.getCustomVersion() != null && pageLayout.getCustomVersion()) {
							pathLayoutMapping.put(pageLayout.getId(), refPage.getPath());
						} else {
							// remove the page reference if it's system layout
							refPage.setLayout(null);
							genericService.merge(refPage);
						}
					}
				}
			}
			
			//em.flush();
			model = getModel(clazz);
			model = (Model) HibernateUtil.unproxy(model);
			model.getLayouts().clear();
			model.getModelFields().clear();
			model.getAvabButtons().clear();
			updateModel();
			
			deleteSystemLayout(layouts);
		}
		this.clazz = clazz;
		this.modelCnName = cnName;
		this.modelEnName = enName;
		mi = beanIntrospector.getModelInfo(clazz.getName());
		return true;
	}

	private boolean forceUseSystemLayout(){
		return "true".equals(systemDataService.getValue(SystemData.FORCE_USE_SYSTEMLAYOUT, true, "false")); 
	}
	
	private void deleteSystemLayout(List<Layout> layouts) {
		// process the custom version
		for (Layout layout : layouts) {
			if (forceUseSystemLayout()){
				layout.setCustomVersion(false);
				layout.setPrevVersion(null);
				genericService.merge(layout);
			} else {
				boolean customVersion = layout.getCustomVersion() != null && layout.getCustomVersion();
				if (customVersion) {
					// for custom version, delete it if it's useless
					if (pathLayoutMapping.get(layout.getId()) == null) {
						genericService.delete(layout, true);
					} else {
						// remove the system version reference
						if (layout.getPrevVersion() != null) {
							layout.setPrevVersion(null);
							genericService.merge(layout);
						}
					}
				}
			}
		}
		
		// process the system version
		for (Layout layout : layouts) {
			boolean customVersion = layout.getCustomVersion() != null && layout.getCustomVersion();
			if (!customVersion) {
				genericService.delete(layout, true);
			}
		}
	}

	private boolean buildFieldsInfoOk = false;

	@Override
	public boolean buildFieldsInfoOk() {
		return buildFieldsInfoOk;
	}

	@Override
	public void setBuildFieldsInfoOk(boolean buildFieldsOk) {
		this.buildFieldsInfoOk = buildFieldsOk;
	}

	@Override
	public void buildFieldsProperty() {
		for (PropertyInfo pi : mi.getAllProperties()) {
			addModelField(pi.getName());
		}
	}

	
	public void buildFields() {
	}
	
	
	public  void buildField(){
		attachModel();
		buildFields();
		genericService.merge(model);
	};
	
	@Override
	public void buildFieldsInfo() {
		if (buildFieldsInfoOk)
			return;
		buildFieldsInfoOk = true;
		
		attachModel();
	}

	@Override
	public void buildButtons() {
//		if (defaultButtons == null) {
		attachModel();
		setDefaultLayoutButtons();
		if (defaultButtons == null){
			defaultButtons = new ArrayList<AvabButton>();
		}
//			
//			defaultButtons.add(createFunctionButton(FunctionButton.PREADD,
//					"Add", "新增"));
//			defaultButtons.add(createFunctionButton(FunctionButton.ADD, "Add",
//					"新增"));
//			defaultButtons.add(createFunctionButton(FunctionButton.UPDATE,
//					"Edit", "修改"));
//			defaultButtons.add(createFunctionButton(FunctionButton.PREUPDATE,
//					"Edit", "修改"));
			defaultButtons.add(createFunctionButton(FunctionButton.SEARCH,
					"Search", "查询"));
//			defaultButtons.add(createFunctionButton(FunctionButton.PRESEARCH,
//					"Search", "查询"));
//			defaultButtons.add(createFunctionButton(FunctionButton.DELETE,
//					"Delete", "删除"));
//			defaultButtons.add(createFunctionButton(FunctionButton.VIEW,
//					"View", "查看"));
//			defaultButtons.add(createFunctionButton(FunctionButton.EXPORT,
//					"Export", "导出"));

//		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.espirit.eap.manager.ModelBuilder#prepareLayoutElements()
	 * builderLayout(builderButtons-->prepareLayoutElements)
	 */

	@Override
	public void prepareLayoutElements() {
		prepareModelFields();
		addSearchFields();
		addListFields();
		addDetailFields();
	}
	
	protected void prepareModelFields(){
	}
	protected void addSearchFields(){
	}
	protected void addListFields(){
	}
	protected void addDetailFields(){
	}

	// prepareLayoutElements

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.espirit.eap.manager.ModelBuilder#buildLayout() 1.model update
	 * sequence：buildField-->buildFieldsInfo-->buildLinkedModel-->buildLayout
	 * 2.buildLayout sequence:buildButtons-->prepareLayoutElements-->buildLayout
	 * 3.buildMiscData(afterBuilderLayout)
	 */

	@Override
	public void buildLayout() {
//		if (this.getClass().getName().equals("com.espirit.sale.manager.init.InstockBuilder") 
//				|| this.getClass().getName().equals("com.espirit.sale.manager.init.SalesOrderBuilder")){
//			System.out.println(this.getClass().getName());	
//		} 
		buildDetailLayout();
		buildListLayout();
		updateModel();
		
		// Update the page layout of the default list and detail layout
		if (model.getLayouts() != null) {
			for (Layout layout : model.getLayouts()) {
				if (layout.getName() != null) {
					if (layout instanceof DetailLayout && layout.getName().endsWith(".detailLayout")) {
						createPage(layout, model.getName(), "_add", false);
						createPage(layout, model.getName(), "_update", false);
					} else if (layout instanceof ListLayout && layout.getName().endsWith(".listLayout")) {
						createPage(layout, model.getName(), "_search", false);
					}
				}
			}
		}
		
		createPageRowClickUrl();
	}

	private boolean buildLayoutOk = false;

	@Override
	public boolean buildLayoutOk() {
		return buildLayoutOk;
	}

	@Override
	public void setBuildLayoutOk(boolean builderLayoutOk) {
		this.buildLayoutOk = builderLayoutOk;
	}

	// builder Layout

	/*
	 * begin: builderMiscData:
	 */

	@Override
	public void buildMiscData() {
		if (!this.buildMiscDataOk) {
			attachModel();
			
			model =  modelService.getModel(clazz);
			defaultButtons = model.getAvabButtons();
			
			afterBuilderLayout();

			updateModel();
			
		}
	}

	@Override
	public final void preBuildMiscData(){
		attachModel();
		preBuildMiscDatas();
		updateModel();
	}
	
	protected void preBuildMiscDatas(){
		
	}
	
	private boolean buildMiscDataOk = false;

	@Override
	public boolean buildMiscDataOk() {
		return this.buildMiscDataOk;
	}

	@Override
	public void setBuildMiscDataOk(boolean builderMiscDataOk) {
		this.buildMiscDataOk = builderMiscDataOk;
	}

	protected void afterBuilderLayout() {
		createTabLayout();
		
		updateModel();
		
		FunctionButton export = getButton(FunctionButton.EXPORT);
		if (export != null){
			bindButtonToLayout(export, model.getDefaultDetailLayout());
		}
		
		FunctionButton importFile = getButton(FunctionButton.IMPORT);
		if (importFile != null){
			bindButtonToLayout(importFile, getModel(getImportClass()).getDefaultDetailLayout());
		}
		
		FunctionButton goNext = getButton(FunctionButton.GONEXT);
		if (goNext != null){
			Model instance = getModel(WFInstance.class.getName());
			bindButtonToLayout(goNext, instance.getDefaultDetailLayout());
		}
		
		FunctionButton backAccept = getButton(FunctionButton.BACKACCEPT);
		if (backAccept != null){
			bindButtonToLayout(backAccept, model.getDefaultListLayout());
		}
	}
	
	protected Class getImportClass(){
		return EapImport.class;
	}
	
	protected FunctionButton getButton(String buttonName){
		List<FunctionButton> buttons = getListLayoutButton();
		for(FunctionButton fb : buttons){
			if (fb.getName().equals(buttonName)){
				return fb;
			}
		}
		return null;
	}

	// end : builderMiscData

	@Override
	public void buildModelInitData() {
		initDefaultData();
	}

	protected void initDefaultData() {

	}

	private FunctionButton createDefaultButton(String buttonName) {
		if (defaultButtons != null){
			for(AvabButton button : defaultButtons){
				if (button.getName().equals(buttonName)){
					return (FunctionButton) button;
				}
			}
		} else {
			defaultButtons = new ArrayList<AvabButton>();
		}
		
		if (buttonName.equals(FunctionButton.PREADD)){
			defaultButtons.add(createFunctionButton(FunctionButton.PREADD,
					"Add", "新增"));
		}
		if (buttonName.equals(FunctionButton.PREUPDATE)){
			defaultButtons.add(createFunctionButton(FunctionButton.PREUPDATE,
					"Edit", "修改"));
		}
		if (buttonName.equals(FunctionButton.DELETE)){
			defaultButtons.add(createFunctionButton(FunctionButton.DELETE,
					"Delete", "删除"));
		}
		
		if (buttonName.equals(FunctionButton.EXPORT)){
			defaultButtons.add(createFunctionButton(FunctionButton.EXPORT,
					"Export", "导出"));
		}
		if (buttonName.equals(FunctionButton.SEARCH)){
			defaultButtons.add(createFunctionButton(FunctionButton.SEARCH,
					"Search", "查询"));
		}
		
		if (buttonName.equals(FunctionButton.IMPORT)){
			defaultButtons.add(createFunctionButton("_importFile", "Batch Import", "批量导入",this.clazz,FunctionButton.IMPORT));
		}
		
		if (buttonName.equals(FunctionButton.REPORT)){
			FunctionButton report = createFunctionButton("report", "Print", "打印",this.clazz,"report",false,null,null);
			defaultButtons.add(report);
		}
		
		if (buttonName.equals(FunctionButton.GONEXT)){
			FunctionButton goNext = createFunctionButton("_goNext", "Refer", "提交",this.clazz,"_goNext",false,null,null);
			defaultButtons.add(goNext);
		}
		
		if (buttonName.equals(FunctionButton.BACKACCEPT)){
			FunctionButton backAccept = createFunctionButton("backAccept", "Back Accept", "反审核",this.clazz,"backAccept",false);
			defaultButtons.add(backAccept);
		}
		
		
		for(AvabButton button : defaultButtons){
			if (button.getName().equals(buttonName)){
				return (FunctionButton) button;
			}
		}
		return null;
	}

	private boolean buildFieldsOk = false;

	@Override
	public Model getResult() {
		if (!existed && !buildFieldsOk) {
			model = genericService.add(model);
//			models.add(model);
			i18nService.addI18n(modelCnName, modelCnName, modelEnName);
			model.setI18n(modelCnName);
			buildFieldsOk = true;
		} else {
			if (model.getI18n() == null) {
				i18nService.addI18n(modelCnName, modelCnName, modelEnName);
				model.setI18n(modelCnName);
			}
			updateModel();
		}
		return model;
	}

	@Override
	public Model getModelData() {
		return model;
	}

	protected boolean needAutoSearch(){
		return true;
	}
	
	protected List<ModelField> convertPropertyToField(String... properties){
		List<ModelField> results = new ArrayList<ModelField>();
		for (String property : properties){
			ModelField modelField = getModelField(property);
			results.add(modelField);
		}
		return results;
	}
	
	protected Element convertFieldToElement(ModelField mf, boolean isListLayout) {
		if (!NEED_ECCN && mf.getName().equals("eccn")){
			logger.debug("eccn not need!");
			return null;
		}
		if (mf == null){
			logger.debug("modelfield is null");
		}
		SectionElement se = new SectionElement();
		
		String tagName = TagAdaptorUtil.getTagName(beanIntrospector, se, mf);
		if (tagName == null) {
			logger.debug("wrong?");
		}
		se.setTagName(tagName);
		se.setName(mf.getName());
		se.setI18n(mf.getI18n());
		se.setVisible(true);
		se.setReadOnly(false);
		se.setRequired(false);
		
		if ("enumselect".equals(tagName)) {
			se.addExProperty("model", mf.getGenericType());
		}
		
		////////////////////////// modelselect & checkboxlist ////////////////////////
		boolean isModelSelect = "modelselect".equals(tagName);
		if (isModelSelect || "checkboxlist".equals(tagName)) {
			Map<String, String> properties = se.getExProperties();
			if (properties == null) {
				properties = new HashMap<String, String>();
			}
			
			if (isModelSelect) {
				// only the search method enable the free input text
				if (isListLayout) {
					// search method
					properties.put("showMoreIcon", "false");
					properties.put("enableFreeText", "true");
					properties.put("addBlankItem", "true");
					properties.put("showAddLink", "false");
				} else {
					properties.put("enableFreeText", "false");
					// update method & required is false
					if (se.getRequired() == null || !se.getRequired()) {
						properties.put("addBlankItem", "true");
						properties.put("isSelectBlankItem", "true");
					}
				}
				properties.put("enableDynamicName", "true");
			}
			
			String genericType = mf.getGenericType();
			if (beanIntrospector.isModel(genericType) >= 0) {
				properties.put("model", genericType);
				
				if (!se.getName().endsWith(".id")) {
					se.setName(se.getName() + ".id");
				}
			}
			
			// property, labelProperty (displayField)
			String labelProperty = getDisplayProperty(mf);
			if (labelProperty == null) {
				labelProperty = "id";
			}
			properties.put("labelProperty", labelProperty);
			if (!se.getName().endsWith(".id")) { 
				properties.put("property", labelProperty);
			}
			
			se.setExProperty(properties);
		}
		
		
		////////////////////////////////////////////////////////////
		
		if (isListLayout) {
			if (mf.getListType() != null && mf.getListType()){
				se.addExProperty("showAddLink", "false");
			}
		}
		
		String seName = se.getName();
		if ("remark".equals(seName)){
			if (!isListLayout){
				se.setTagName("textarea");
			}
		}
		
		if (exProperties != null){
			if (!isListLayout){
				String[] required = exProperties.get(REQUIRED);
				if (required != null){
					boolean contained = Arrays.asList(required).contains(mf.getName());
					if (contained){
						se.setRequired(true);
					}
				}
			}
		}
		
		PropertyInfo pi = mi.getProperty(mf.getName());
		if (pi != null){
			Method method = pi.getReadMethod();
			if (method != null){
				Dict dict = method.getAnnotation(Dict.class);
				if (dict != null){
					se.setTagName("dictselect");
					se.addExProperty("name", seName);
					se.addExProperty("type", dict.type());
				}
			}
		}
		
		
		for(String dept : deptTrees){
			if (seName.equals(dept)){
				setElementTreebox(se, "fn.service('espirit.eap.manager.department').allDepartments");
			}
		}
		for(String workGroup : workGroupIds){
			if (seName.equals(workGroup)){
				setElementTreebox(se, "fn.service('espirit.eap.manager.workGroup').allData");
			}
		}
		
		for(String building : buildingTrees){
			if (seName.equals(building)){
				setElementTreebox(se, "fn.service('pms.house.building').allBuildings");
			}
		}
		
		if (seName.equals("createdOn") || seName.endsWith("Time")){
			se.addExProperty("format", "yyyy-MM-dd HH:mm");
		}
		return se;
	}
	
	private String getDisplayProperty(ModelField mf){
		String genericType = mf.getGenericType();
		if (genericType != null && beanIntrospector.isModel(genericType) >= 0){
//			return getGenericModel().getDisplayProperty();
			Class clazz = ClassUtil.forName(genericType);
			if (clazz != null) {
				ModelDisplayProperty mdp = (ModelDisplayProperty)clazz.getAnnotation(ModelDisplayProperty.class);
				if (mdp != null){
					return mdp.value();
				}
			}
		}
		return mf.getName();
	}
	
	
	
	protected Element[] convertFieldToElement(ModelField[] fields, boolean isListLayout) {
		Element[] elements = new Element[fields.length];
		int i = 0;
		for (ModelField mf : fields) {
			Element se = convertFieldToElement(mf, isListLayout);
			elements[i++] = se;
		}
		return elements;
	}
	
	protected List<Element> convertFieldToElement(List<ModelField> fields, boolean isListLayout) {
		List<Element> elements = new ArrayList<Element>(fields.size());
		for (ModelField mf : fields) {
			Element se = convertFieldToElement(mf, isListLayout);
			elements.add(se);
		}
		return elements;
	}
	
	protected void buildListLayout() {
		if (this.searchFields.size() == 0 && this.listElements.size() == 0)
			return;
		
//		setDefaultLayoutButtons();
		addExtraListLayoutButtons();
		
		ListLayout ll = getDefaultListLayout();
		
		// calculate the element count
		int btnCount = getListLayoutButton().size();
		int totalCount = this.searchFields.size() + btnCount;
		
		if (totalCount > 7) {
			// show the element in 2 lines
			// search container
			if (this.searchFields.size() > 0) {
				FlatSection searchSection = ll.createFlatSection();
				searchSection.setName("model");
				searchSection.setNamePrefix("model");
				searchSection.addExProperty("clazz", "pageinfo-header searchInputCtn");
				searchSection.setColsPerRow(getListLayoutSearchColsPerRow());
				
				List<Element> elements = this.searchFields;
				FunctionButton searchBtn = createDefaultButton(FunctionButton.SEARCH);
				searchBtn.addExProperty("showMoreIcon", "true");
				searchBtn.addExProperty("clazz", "blue");
				searchBtn.addExProperty("style", "margin-left:8px");
				elements.add(searchBtn);
				elementService.createSection(searchSection, elements);
			}
			
			// button container
			if (this.listElements.size() > 0) {
				// create default button.
				buildListLayoutButton();
				
				// add button
				if (btnCount > 0) {
					FlatSection btnSection = (FlatSection)getDefaultListLayout().getChildren().get(0);
					if (btnSection != null && btnSection.getChildren() != null){
						FunctionButton addButton = (FunctionButton) btnSection.getChild(FunctionButton.PREADD);
						if (addButton != null) {
							addButton.addExProperty("clazz", "blue buttonAdd");
						}
					} else {
						logger.error("empty buttons:" + model.getName());
					}
				}
			}
		} else {
			// show them in the same line
			// button container
			if (this.listElements.size() > 0) {
				// create default button.
				buildListLayoutButton();
				
				// add button
				if (btnCount > 0) {
					FlatSection btnSection = (FlatSection)getDefaultListLayout().getChildren().get(0);
					if (btnSection != null){
						FunctionButton addButton = (FunctionButton) btnSection.getChild(FunctionButton.PREADD);
						if (addButton != null) {
							addButton.addExProperty("clazz", "blue buttonAdd");
						}
					}
				}
			}
			
			// search container
			if (btnCount > 0 || this.searchFields.size() > 0) {
				FlatSection searchSection;
				if (btnCount == 0) {
					searchSection = getDefaultListLayout().createFlatSection();
				} else {
					// share the search container
					searchSection = (FlatSection)getDefaultListLayout().getChildren().get(0);
				}
				searchSection.setColsPerRow(100);
				searchSection.setTagName("container");
				searchSection.addExProperty("clazz", "pageinfo-header dynamicButtonNav searchInputCtn");
				searchSection.setName("model");
				searchSection.setNamePrefix("model");
				
				List<Element> elements = this.searchFields;
				if (elements != null && elements.size() > 0) {
					FunctionButton searchBtn = createDefaultButton(FunctionButton.SEARCH);
					searchBtn.addExProperty("showMoreIcon", "true");
					searchBtn.addExProperty("clazz", "blue");
					searchBtn.addExProperty("style", "margin-left:8px");
					elements.add(searchBtn);
				}
				elementService.createSection(searchSection, elements);
			}
		}
		
		if (this.listElements.size() > 0) {
			// create grid
			GridSection fs_list = ll.createGridSection();
			fs_list.setNamePrefix("execute");
			if (searchFields.size() == 0 && this.needAutoSearch()) {
				fs_list.setDisableAutoSearch(Boolean.FALSE);
			}
			
			elementService.createSection(fs_list, listElements);
			
			addFooter(fs_list);
		}

		// add extra list layout
		if (this.extraListElements != null) {
			for (Iterator it = this.extraListElements.keySet().iterator(); it
					.hasNext();) {
				String listName = (String) it.next();
				List<Element> elements = this.extraListElements
						.get(listName);

				ListLayout extra = createListLayout(model, listName);

				// create grid
				GridSection fs_extra = extra.createGridSection();
				fs_extra.setNamePrefix("execute");
				fs_extra.setDisableAutoSearch(true);
				elementService.createSection(fs_extra, elements);
			}
		}
		
	}
	
	protected void buildListLayout(String prefix, FlatSection section) {
		List<SectionElement> sectionElements = null;
		for (SectionElement se : section.getSectionElements()) {
			if (sectionElements == null) {
				sectionElements = new ArrayList<SectionElement>();
			}
			SectionElement clone = se.cloneSelf();
			clone.setName(prefix + "." + se.getName());
			sectionElements.add(clone);
		}
		if (sectionElements == null)
			return;
		buildListLayoutButton();
		getDefaultListLayout().createGridSection()
				.createExistedSectionElements(sectionElements);
	}

	private Layout getAndCreateDefaultLayout(Class layoutClazz) {
		Layout layout;
		if (ListLayout.class.equals(layoutClazz)) {
			layout = getAndCreateModel().getDefaultListLayout();
		} else {
			layout = getAndCreateModel().getDefaultDetailLayout();
		}
		
		if (layout == null) {
			if (ListLayout.class.isAssignableFrom(layoutClazz)) {
				layout = createListLayout(getAndCreateModel(), null);
			} else {
				layout = createDetailLayout(getAndCreateModel(), null, true);
			}
			
			// set the version date to 2014/6/20
			Calendar now = Calendar.getInstance();
			now.set(Calendar.YEAR, 2015);
			now.set(Calendar.MONTH, 1);
			now.set(Calendar.DAY_OF_MONTH, 6);
			layout.setCreatedOn(now.getTime());
			layout.setModifiedOn(now.getTime());
			layout.setDefaulted(true);
		}
		return layout;
	}
		
	public ListLayout createListLayout(Model model, String name) {
		ListLayout ll = new ListLayout();
		ll.setDefaulted(false);
		name = getListLayoutName(name);
		ll.setName(name);
		ll.setModel(model);
		model.safeLayouts().add(ll);
		return ll;
	}
	
	private String getListLayoutName(String name){
		if (name == null){
			return '_' + model.getName() + ".listLayout";
		} else {
			return "_" + model.getName() + '.' + name;
		}
	}

	public DetailLayout createDetailLayout(Model model, String name , boolean hasButtonArea) {
		DetailLayout dl = new DetailLayout();

		dl.setDefaulted(true);
		
		if (hasButtonArea){
			FlatSection lastFlatSection = dl.createLastFlatSection();
			PageReference pageReference = new PageReference();
			pageReference.setUrl("/pages/espirit/common/jdetaillayout.tpl.ftl");
			Element se = elementService.createSectionElement(lastFlatSection, pageReference);
			se.setPositionCol(1000);  //hard code first..
		}
		
		if (name == null){
			name = '_' + model.getName() + ".detailLayout";
		} else {
			name = "_" + model.getName() + '.' + name;
		}
		dl.setName(name);
		dl.setModel(model);
		model.safeLayouts().add(dl);
		return dl;
	}
	
	protected DetailLayout createDetailLayout(Model model, String layoutName , Boolean defaulted, List<ModelField> modelFields){
		DetailLayout recal = createDetailLayout(model, layoutName, true);
		FlatSection fs = recal.createFlatSection();
		List<Element> elements = new ArrayList<Element>();
		fillElements(elements, modelFields);
		elementService.createSection(fs, elements);
		recal.setDefaulted(defaulted);
		updateModel();
		
		DetailLayout dl = getDetailLayout(this.clazz, layoutName);
		return dl;
	}
	
	protected ListLayout createListLayout(Model model, String layoutName , Boolean defaulted, List<ModelField> modelFields){
		ListLayout recal = createListLayout(model, layoutName);
		recal.setDefaulted(defaulted);
		FlatSection fs = recal.createGridSection();
		fs.setTagName("datagrid");
		List<Element> elements = new ArrayList<Element>();
		fillElements(elements, modelFields);
		elementService.createSection(fs, elements);
		updateModel();
		
		ListLayout dl = getListLayout(this.clazz, layoutName);
		return dl;
	}
	
	public DetailLayout getDetailLayout(String modelName, String name) {
		return layoutService.getDetailLayout(modelName, name);
	}
	public DetailLayout getDetailLayout(Class modelClass, String name) {
		String simpleName = beanIntrospector.getSimpleName(modelClass.getName());
		return layoutService.getDetailLayout(simpleName, name);
	}
	
	public ListLayout getListLayout(String modelName, String name) {
		return layoutService.getListLayout(modelName, name);
	}
	
	public ListLayout getListLayout(Class modelClass, String name) {
		String simpleName = beanIntrospector.getSimpleName(modelClass.getName());
		return getListLayout(simpleName, name);
	}
	

	protected ListLayout getDefaultListLayout() {
		return (ListLayout)getAndCreateDefaultLayout(ListLayout.class);
	}

	protected DetailLayout getDefaultDetailLayout() {
		return (DetailLayout)getAndCreateDefaultLayout(DetailLayout.class);
	}

	private void buildListLayoutButton() {
		if (getListLayoutButton() == null || getListLayoutButton().size() == 0)
			return;
		FlatSection btnSection = getDefaultListLayout().createFlatSection();
		btnSection.setColsPerRow(100);
		btnSection.addExProperty("clazz", "dynamicButtonNav");
		for (FunctionButton fb : getListLayoutButton()) {
			if (fb != null){
				elementService.createSectionElement(btnSection, fb);
			}
		}
	}

	private List<FunctionButton> buttons = null;

	protected final List<FunctionButton> getListLayoutButton() {
		if (buttons == null) {
			buttons = new ArrayList<FunctionButton>();
		}
		return buttons;
	}

	protected void setDefaultLayoutButtons() {
		addButton(FunctionButton.PREADD);
		addButton(FunctionButton.DELETE);
		addButton(FunctionButton.EXPORT);
	}
	
	protected void addButton(String buttonName){
		getListLayoutButton().add(createDefaultButton(buttonName));
	}
	
	protected void addButton(FunctionButton button){
		getListLayoutButton().add(button);
	}
	
	protected void addExtraListLayoutButtons() {

	}

	protected final void addExtraButton(FunctionButton button) {
		if (button != null) {
			button = (FunctionButton) getAndCreateModel().getAvabButton(button.getName());
			getListLayoutButton().add(button);
		}
	}

	protected Integer getDetailLayoutSectionColsPerRow() {
		return COLS_PERROW;
	}
	
	protected Integer getListLayoutSearchColsPerRow(){
		return LIST_COLS_PERROW;
	}

	protected void buildDetailLayout() {
		if (this.detailElements.size() == 0)
			return;
		FlatSection section = getDefaultDetailLayout().createFlatSection();

		section.setColsPerRow(getDetailLayoutSectionColsPerRow());
		section.setNamePrefix("model");
		elementService.createSection(section, detailElements);
		
		addFooter(section);
	}
	
	private void addFooter(FlatSection section){
//		SectionElement se = new SectionElement();
//		se.setName("footer");
//		se.setTagName("footer");
//		elementService.createSectionElement(section, se);
	}

	protected FunctionButton createFunctionButton(String name, String enName,
			String cnName) {
		return createFunctionButton(name, enName, cnName, null, name);
	}

	protected FunctionButton createFunctionButton(String name, String enName,
			String cnName, Class<?> modelClass, String method) {
		return createFunctionButton(name, enName, cnName, modelClass, method,
				false);
	}

	protected FunctionButton createFunctionButton(String name, String enName,
			String cnName, Class<?> modelClass, String method, Boolean submit) {
		return createFunctionButton(name, enName, cnName, modelClass, method,
				submit, null, null);
	}

	@SuppressWarnings("unchecked")
	protected FunctionButton createFunctionButton(String name, String enName,
			String cnName, Class modelClass, String method, Boolean submit,
			Element target, Layout layout) {
		return createFunctionButton(name, enName, cnName, modelClass, method, submit, target, layout, false);
	}
	
	protected FunctionButton createFunctionButton(String name, String enName,
			String cnName, Class modelClass, String method, Boolean submit,
			Element target, Layout layout ,boolean clickEventWithUrl) {
		FunctionButton fb = new FunctionButton();
		fb.setName(name);
		
		fb.setTagName("button");
		String modelClassName;
		if (modelClass == null) {
			modelClassName = this.model.getName();
		} else {
			modelClassName = modelClass.getName();
		}
		String modelName = beanIntrospector.getSimpleName(modelClassName);
		String url = functions.linkModelNoBase(modelName, method);
		fb.addExProperty("url", url);
		String onclickEvent;
		if (submit != null && submit) {
			// TODO: target == '_blank'
			String parameters = target == null ? "" : FrameConstant.PARAM_RESULTCODE + "=$result&skipForm=true";
			onclickEvent = "submitPage(this,'" + parameters + "')";
		} else {
			if ("search".equals(method)) {
				onclickEvent = "searchRecord(this)";
			} else {
				if (clickEventWithUrl){
					onclickEvent = "openMainTabFromBtn(this,g_base_site+'" + url + "')";
				} else {
					onclickEvent = "openMainTabFromBtn(this,'" + method + "')";	
				}
			}
		}
		fb.addExProperty("onclick", onclickEvent);
		
		
		if (layout != null) {
			bindButtonToLayout(fb, layout);
		}
		fb.setI18n(cnName);
		i18nService.addI18n(cnName, cnName, enName);
		fb = genericService.merge(fb);
		
		model.createAvabButton(fb);
		genericService.merge(model);
		fb = (FunctionButton) model.getAvabButton(name);
		return fb;
	}

	protected Model getModel(Class<?> modelClazz) {
		Model obj =  modelService.getModel(modelClazz);
		obj = (Model) HibernateUtil.unproxy(obj);
		return obj;
	}

	protected Model getModel(String modelClassName) {
		Model retModel =  modelService.getModel(modelClassName);
		return retModel;
	}

	protected Model getAndCreateModel() {
		if (model == null) {
			model = new Model();
			model.setName(mi.getSimpleName());
		}
		return model;
	}

	private Model getGenericModel(Class<?> clazz) {
		Model genericModel = new Model();
		
		return genericModel;
	}

	private boolean existsClass(Class<?> clazz) {
		Model existedModel = getModel(clazz);
		if (existedModel != null) {
			model = existedModel;
			existed = true;
		}
		return existed;
	}

	private void addModelField(ModelField mf) {
		if (mf != null) {
			modelFields.add(mf);
		}
	}

	protected void addSearchField(ModelField ... fields) {
		if (fields != null) {
			for (ModelField mf : fields) {
				Element element = convertFieldToElement(mf, true);
				searchFields.add(element);
			}
		}
	}
	
	protected void addSearchField(List<ModelField> fields){
		if (fields != null) {
			for (ModelField mf : fields) {
				Element element = convertFieldToElement(mf, true);
				searchFields.add(element);
			}
		}
	}

	protected void releaseSearchFields() {
		searchFields.clear();
	}

	protected void addListField(ModelField... fields) {
		if (fields != null) {
			for (int i = 0; i < fields.length; i++) {
				ModelField mf = fields[i];
				if (mf != null) {
					Element element = convertFieldToElement(mf, true);
					listElements.add(element);
				} else {
					logger.error("Found null model field in class: " + this.getClass().getName() + ", index=" + i);
				}
			}
		}
	}

	protected List<Element> getListField() {
		return listElements;
	}

	protected void addListField(List<ModelField> fields) {
		if (fields != null) {
			for (ModelField mf : fields) {
				addListField(mf);
			}
		}
	}
	
	protected void addDetailField(List<ModelField> fields) {
		if (fields != null) {
			for (ModelField mf : fields) {
				addDetailField(mf);
			}
		}
	}

	protected void addExtraListFields(Map<String, List<Element>> maps) {
		if (this.extraListElements == null) {
			this.extraListElements = new HashMap<String, List<Element>>();
		}
		for (Iterator it = maps.keySet().iterator(); it.hasNext();) {
			String listName = (String) it.next();
			List<Element> fields = maps.get(listName);
			this.extraListElements.put(listName, fields);
		}
	}

	protected Map<String, List<Element>> getExtraListFields() {
		return this.extraListElements;
	}

	protected void addDetailField(ModelField... fields) {
		if (fields != null) {
			for (ModelField mf : fields) {
				if (mf != null) {
					Element element = convertFieldToElement(mf, false);
					detailElements.add(element);
				}
			}
		}

	}
	
	protected void fillElements(List<Element> elements, List<ModelField>  fields) {
		if (fields != null) {
			for (ModelField mf : fields) {
				if (mf != null) {
					Element element = convertFieldToElement(mf, false);
					elements.add(element);
				}
			}
		}

	}
	
	protected SectionElement safeElement(List<Element> elements, String fieldName) {
		for (Element element : elements) {
			if (element == null) continue;
			if (fieldName.equals(element.getName())) {
				return (SectionElement) element;
			}
		}
		return new SectionElement();
	}
	
	protected void setRequired(String fieldName) {
		safeElement(detailElements, fieldName).setRequired(Boolean.TRUE);
	}
	
	protected void setWidth(List<Element> elements, String fieldName, Integer width) {
		safeElement(elements, fieldName).setWidth(width);
	}
	
	protected void setReadOnly(List<Element> elements, String fieldName) {
		safeElement(elements, fieldName).setReadOnly(Boolean.TRUE);
	}
	
	protected void setCurrency(List<Element> elements, String fieldName, String currency) {
		SectionElement se = safeElement(elements, fieldName);
		se.addExProperty("currency", currency);
		se.setTagName("currency");
	}

	protected List<Element> getDetailField() {
		return detailElements;
	}

	protected Model builderModel(ModelBuilder builder) {
		ModelDirector director = new ModelDirector(builder);
		director.contruct();
		builder.getResult();

		director.buildFieldsInfo();
//		director.buildLinkedModel();
		director.buildLayout();

		director.buildMiscData();

		return builder.getModelData();
	}

	protected ModelField addModelField(String property){
		ModelField mf = getAndCreateModel().createModelField(property);
		PropertyInfo pi = mi.getProperty(property);
		addModelField(mf);
		mf.setGenericType(pi.getGenericType());
		boolean listType = List.class.getName().equals(pi.getType());
		mf.setListType(listType);
		mf.setTransientProperty(!pi.isTableColumn());
		return mf;
	}
	
	protected ModelField addModelField(String property, String enName,
			String cnName) {
		ModelField mf = getModelField(property);
		
		if (mf.getId() == null) {
			// find the real model field & update the i18n
			ModelPropertyInfo info = beanIntrospector.getModelPropertyInfo(getAndCreateModel().getName(), property);
			String modelName = info.modelInfo.getSimpleName();
			ModelField realModelField = modelService.getModelField(modelName, info.propertyInfo.getName());
			if (cnName != null || enName != null) {
				I18n i18n = i18nService.addI18n(cnName, enName);
				realModelField.setI18n(i18n.getKey());
			}
			mf = genericService.merge(realModelField);
			
//			mf.setI18n(realModelField.getI18n());
		} else {
			if (cnName != null || enName != null) {
				I18n i18n = i18nService.addI18n(cnName, enName);
				mf.setI18n(i18n.getKey());
			}
			genericService.merge(mf);
		}
		return mf;
	}
	

	protected ModelField getModelField(String fieldName) {
		ModelField mf = null;
		int index = fieldName.indexOf('.');
		if (index == -1) {
			mf = getAndCreateModel().getModelField(fieldName);		
		}
		if (mf == null) {
			ModelPropertyInfo info = beanIntrospector.getModelPropertyInfo(getAndCreateModel().getName(), fieldName);
			
			ModelField mock = new ModelField();
			mock.setName(fieldName);
			mock.setGenericType(info.propertyInfo.getGenericType());
			mock.setListType(List.class.getName().equals(info.propertyInfo.getType()));
			ModelField existedMf = modelService.getModelField(info.modelInfo.getSimpleName(), info.propertyInfo.getName());
			if (existedMf == null) {
				logger.error("Found wrong model field: " + model.getName() + '#' + fieldName);
			} else {
				String i18n = existedMf.getI18n();
				mock.setI18n(i18n);
			}
			mf = mock;
		}
		return mf;
	}

	
	private boolean isDomainModel(String name) {
		try {
			Class<?> clazz = ClassLoaderUtil.loadClass(name, this.getClass());
			return clazz
					.getAnnotation(com.espirit.eap.annotation.DomainModel.class) != null;
		} catch (ClassNotFoundException ex) {
			return false;
		}
	}

	private String getElementNameByModelField(String fieldName) {
		ModelField mf = getAndCreateModel().getModelField(fieldName);
		if (mf == null) {
			if (logger.isInfoEnabled()) {
				logger.info("Find the null property, please check : model#field is "
						+ getAndCreateModel().getName() + "#" + fieldName);
			}
			return null;
		}
		return getExtraPropertyName(mf.getGenericType(), fieldName);
	}
	
	private String getExtraPropertyName(String fieldType, String fieldName){
		// TODO: treat i18n as a whole entity, do we need change the other entities, such as drop down list?
		if (fieldType != null && !"eap.manager.i18n".equals(fieldType)
				&& !fieldName.equals("partNumber") && !fieldName.equals("intPartNumber")){
			if (beanIntrospector.isModel(fieldType) >= 0) {
				Class genericModelClass = ClassUtil.getClass(fieldType);
				if (genericModelClass != null && genericModelClass.isEnum()){
					return fieldName;
				} else {
					return fieldName + ".id";
				}
			} else {
				return fieldName;
			}
		} else {
			return fieldName;
		}
	}
	
	protected <T> T addModel(T model){
		return genericServiceUtil.checkAndAdd(model);
	}
	
	protected void attachModel(){
//		boolean contain = em.contains(model);
//		if (!contain){
//			model = genericService.merge(model);
//		}
	}
	
	private void updateModel(){
		genericService.merge(model);
	}
	
	protected void updateObject(Object obj){
		genericService.merge(obj);
	}
	
	protected void setEditElementRequired(String modelField , boolean required){
		SectionElement se = getEditSectionElement(modelField);
		if (se != null){
			se.setRequired(required);
		}
	}
	
	protected void setEditElementReadOnly(String modelField , boolean readOnly){
		SectionElement se = getEditSectionElement(modelField);
		if (se != null){
			se.setReadOnly(readOnly);
		}
	}
	
	protected void setEditElementVisible(String modelField , boolean visible){
		SectionElement se = getEditSectionElement(modelField);
		if (se != null){
			se.setVisible(visible);
		}
	}
	
	protected void setEditElementWidth(String modelField , Integer width){
		SectionElement se = getEditSectionElement(modelField);
		if (se != null){
			se.setWidth(width);
		}
	}
	protected void setSearchResultElementWidth(GridSection gridSection , String modelField , Integer width){
		SectionElement se = getSearchResultSectionElement(gridSection , modelField);
		if (se != null){
			se.setWidth(width);
		}
	}
	
	protected SectionElement getEditSectionElement(String fieldName){
		if (model.getDefaultDetailLayout() == null) return null;
		return model.getDefaultDetailLayout().findSectionElement(fieldName);
	}
	
	protected SectionElement getSearchResultSectionElement(GridSection gridSection , String fieldName){
		String elementName = getElementNameByModelField(fieldName);
		return gridSection.getSectionElement(elementName);
	}
	
	protected void disableAddLinkSectionElement(ModelField ... modelFields){
		if (modelFields == null || model == null || model.getDefaultDetailLayout() == null) return ;
		DetailLayout detailLayout = (DetailLayout)model.getDefaultDetailLayout();
		for(ModelField modelField : modelFields){
			SectionElement disableElement = detailLayout.getSectionElementFromFlatSection(modelField.getName() + ".id");
			if (disableElement != null){
				disableElement.addExProperty("showAddLink", "false");
			}
		}
	}
	
	protected void bindButtonToLayout(FunctionButton btn, Layout layout) {
		if (layout == null) {
			return;
		}
		
		String url = btn.getExProperty("url");
		String model;
		String method;
		if (url != null) {
			Invoker invoker = routing.path2Invoker(url, false);
			model = beanIntrospector.getSimpleName(invoker.actionName);
			method = invoker.methodName;
		} else {
			model = btn.getModel();
			if (model == null) {
				model = btn.getOwnerModel().getName();
			}
			method = btn.getMethod();
		}
		createPage(layout, model, method, true);
		
		genericService.merge(layout);
	}

	protected Page createPage(Layout layout, String model, String method, boolean enableOverride) {
		String path = functions.linkModelNoBase(model, method);
		
		Page pageExample = new Page();
		pageExample.setPath(path);
		
		Page existed = genericService.searchAll(pageExample).getData(0);
		boolean customLayout = false;
		if (!enableOverride && existed != null && existed.getLayout() != null) {
			Boolean customVersion = existed.getLayout().getCustomVersion();
			customLayout = customVersion != null && customVersion;
			if (!customLayout) {
				return existed;
			}
		}
		
		if (existed == null) {
			existed = new Page();
			existed.setPath(path);
			existed.setModifyOn(Calendar.getInstance().getTime());
			existed.setMethod(method);
			existed.setTemplate(false);
			existed.setFreeAccess(false);
			existed.setCommonTemplate(false);
		} else {
			if (!customLayout && existed.getLayout() != null && !existed.getLayout().getId().equals(layout.getId())) {
				logger.warn("Warning, found duplicated path:" + path
						+ ", change layout from " + existed.getLayout().getId() + " to " + layout.getId());
			}
		}
		
		if (customLayout && pathLayoutMapping != null && pathLayoutMapping.size() > 0) {
			String oldPath = pathLayoutMapping.get(existed.getLayout().getId());
			if (oldPath != null) {
				// TODO: use 'while' to find the top custom version if the custom version is multiple
				// update the old previous version
				existed.getLayout().setPrevVersion(layout);
				
				// keep the old custom layout
				layout = existed.getLayout();
			}
		}
		
		existed.setLayout(layout);
		genericService.merge(existed);
		return existed;
	}
	
	private void createPageRowClickUrl(){
		RelatedModel[] relatedModels = getRelatedModels();
		if (relatedModels == null ) return ;
		String[] urls = new String[relatedModels.length];
		for(int i = 0; i < relatedModels.length; i++){
			RelatedModel relatedModel = relatedModels[i];
			urls[i] = getUrl(relatedModel);
		}
		
		createPageRowClickUrl(urls);
	}
	
	protected void createTabLayout(){
		RelatedModel[] relatedModels = getRelatedModels();
		if (relatedModels == null ) return ;

		createGetLayout();
	}
	
	private void createGetLayout(){
		TabbedModel[] tabbedModels = getTabbedModels();
		if (tabbedModels == null || tabbedModels.length == 0) return ;
		Layout layout = new Layout();
		String name = '_' + model.getName() + ".tabbedsection";
		layout.setName(name);
		
		TabbedSection tabbedSection = elementService.createTabbedSection(layout, tabbedModels.length);
		for(int i = 0 ; i < tabbedModels.length ; i ++){
			FlatSection fs = tabbedSection.getTabPage(i);
			TabbedModel tabbedModel = tabbedModels[i];
			Model tabModel = modelService.getModel(tabbedModel.getClazz());
			if (tabModel == null) continue;
			String i18n = tabModel.getI18n();
			if (i18n == null){
//				Model tabModel = modelService.getModel(tabbedModel.getClazz());
				if (tabModel != null){
				  i18n = tabModel.getI18n();
				} else {
					logger.debug("empty tab model?");
				}
			} else {
				
			}
			fs.setI18n(i18n);
			fs.addExProperty("url", getUrl(tabbedModel));
			
			if (SEARCHRELATEDRESULT.equals(tabbedModel.getMethod())){
				createRelatedLayout(tabbedModel.getClazz(), SEARCHRELATEDRESULT);
			}
		}
		model.safeLayouts().add(layout);
		layout.setModel(model);
		updateObject(model);
		// find the saved layout
		layout = model.getLayoutByName(name);
		
		createPage(layout, model.getName(), "getLayout", true);
	}
	
	protected void createRelatedLayout(Class modelClazz, String method){
		createRelatedLayout(modelClazz, method, (FunctionButton[])null);
	}
	
	protected void createRelatedLayout(Class modelClazz, String method, FunctionButton... buttons){
		ListLayout existed = getListLayout(modelClazz, method);
		if (existed == null){
			Model resultModel = modelService.getModel(modelClazz);
			ListLayout methodLayout = createListLayout(resultModel, method);
			
			if (buttons != null ){
				FlatSection buttonArea = methodLayout.createFlatSection();
				buttonArea.setColsPerRow(100);
				elementService.addSectionElement(buttonArea, buttons);
			}
			
			String searchResultLayoutName = methodLayout.getName();
			GridSection source = (GridSection) resultModel.getDefaultListLayout().getMainGridSection();
			if (source == null){
				logger.error("error source has not grid section:" + resultModel.getName());
			} else {
				GridSection gs = (GridSection) resultModel.getDefaultListLayout().getMainGridSection().cloneSelf();
				methodLayout.safeChildren().add(gs);
				
				updateObject(resultModel);
				
				methodLayout = (ListLayout) resultModel.getLayoutByName(searchResultLayoutName);
				if (methodLayout != null){
					createPage(methodLayout, resultModel.getName(), method, true);
				}
			}
		}
	}
	
	protected void createRelatedLayoutWithSearch(Class modelClazz, String method, List<ModelField> searchFields, FunctionButton... buttons){
		//default one section include : search,button
		ListLayout existed = getListLayout(modelClazz, method);
//		if (existed == null){
			Model resultModel = modelService.getModel(modelClazz);
			ListLayout methodLayout = createListLayout(resultModel, method);
			FlatSection buttonArea = null;
			if (buttons != null ){
				buttonArea = methodLayout.createFlatSection();
				buttonArea.setColsPerRow(100);
				elementService.addSectionElement(buttonArea, buttons);
			}
			
			if (searchFields.size() > 0) {
				FlatSection searchSection;
//				if (btnCount == 0) {
//					searchSection = methodLayout.createFlatSection();
					if (buttonArea == null){
						buttonArea = methodLayout.createFlatSection();
					}
					searchSection = buttonArea;
//				} else {
//					// share the search container
//					searchSection = (FlatSection)getDefaultListLayout().getChildren().get(0);
//				}
				searchSection.setColsPerRow(100);
				searchSection.setTagName("container");
				searchSection.addExProperty("clazz", "pageinfo-header dynamicButtonNav searchInputCtn");
				searchSection.setName("model");
				searchSection.setNamePrefix("model");
				
				List<Element> elements = new ArrayList<Element>();
				for(ModelField mf : searchFields){
					Element element = convertFieldToElement(mf, true);
					elements.add(element);
				}
				if (elements != null && elements.size() > 0) {
					FunctionButton searchBtn = createDefaultButton(FunctionButton.SEARCH);
					searchBtn.addExProperty("showMoreIcon", "true");
					searchBtn.addExProperty("clazz", "blue");
					searchBtn.addExProperty("style", "margin-left:8px");
					elements.add(searchBtn);
				}
				elementService.createSection(searchSection, elements);
			}
			
			
			String searchResultLayoutName = methodLayout.getName();
			GridSection source = (GridSection) resultModel.getDefaultListLayout().getMainGridSection();
			if (source == null){
				logger.error("error source has not grid section:" + resultModel.getName());
			} else {
				GridSection gs = (GridSection) resultModel.getDefaultListLayout().getMainGridSection().cloneSelf();
				methodLayout.safeChildren().add(gs);
				
				updateObject(resultModel);
				
				methodLayout = (ListLayout) resultModel.getLayoutByName(searchResultLayoutName);
				if (methodLayout != null){
					createPage(methodLayout, resultModel.getName(), method, true);
				}
			}
//		}
	}
	
	protected RelatedModel[] getDefaultRelatedModels(){
		RelatedModel getLayout = new RelatedModel(this.clazz, "getLayout");
		return new RelatedModel[]{getLayout};
	}
	
	protected RelatedModel[] getRelatedModels(){
		return null;
	}
	
	protected TabbedModel[] getTabbedModels(){
		return null;
	}
	
	private void createPageRowClickUrl(String[] urls ){
		if (this.getDefaultListLayout() == null || this.getDefaultListLayout().getMainGridSection() == null) {
			return;
		}
		GridSection pageInfo = this.getDefaultListLayout().getMainGridSection();
		pageInfo.createRowClickLinks(urls);
		updateObject(pageInfo);
	}
	
	private String getUrl(LinkModel linkModel){
		return getUrl(linkModel.getClazz(), linkModel.getMethod(), linkModel.getParameter());
	}
	
	private String getUrl(Class  clazz, String method, String exParameter) {
		if (clazz == null){
			logger.debug("class is null!" );
			return "";
		}
		if (method == null || method.length() == 0) {
			method = "search";
		}
		String url = functions.linkModelNoBase(clazz.getName(), method);
		StringBuilder parameter = new StringBuilder();
		
		if (method.startsWith("search")){
			parameter.append("&newSearch=true&nested=true");
		}
		
		if (exParameter != null) {
			parameter.append(exParameter);
		}
		
		return parameter.length() > 0 ? url + '?' + parameter.substring(1) : url;
	}
	
	protected String getI18nKey(String cnName, String enName){
		I18n i18n = i18nService.addI18n(cnName, enName);
		return i18n.getKey();
	}
	
	protected Map<String, String[]> exProperties = new HashMap<String, String[]>();
	protected String[] trees ;
	protected  void gatherExproperties(){
	}
	
	protected void transferShoppingCarItemUrl(AvabButton button){
		return ;
		/*
		String modelName = beanIntrospector.getSimpleName(ShoppingCarItem.class.getName());
		String url = functions.linkModelNoBase(modelName, button.getName());
		button.addExProperty("url", url);
		*/
	}
	
	protected void setElementTreebox(Element element, String service){
		Map<String, String> exDataTypeProperties = new HashMap<String, String>();
		exDataTypeProperties.put("collection", service);
		element.setExProperty(exDataTypeProperties);
		element.setTagName("treebox");
	}

	
	protected LinkedButton createActiveLinkedButton(){
		LinkedButton activeButton = new LinkedButton();
		activeButton.setName("activeFlag");
		activeButton.setImage("/static/espirit/images/default/flag-{0}.gif");
		activeButton.setMethod("setActive");
		activeButton = genericService.add(activeButton);
		
		I18n i18n = i18nService.addI18n("状态", "Active");
		activeButton.setI18n(i18n.getKey());
		
		activeButton.setModel(beanIntrospector.getSimpleName(this.clazz.getName()));
		model.createAvabButton(activeButton);
		return activeButton;
	}
	
	protected GridSection getMainGridSection(List<Element> elements) {
		for(Element child : elements){
			if (child instanceof GridSection){
				return (GridSection) child;
			}
		}
		return null;
	}
}
